<?php

declare(strict_types=1);

namespace app\backend\controller;

use app\common\controller\Basic;
use think\facade\Db;
use app\common\service\sys\UserService;
use app\common\service\MakecodeService;

/**
 * 代码生成器
 * 此功能，有直接读取数据库结构的，代码没有放数据访问层。因读取的不是本功能表。是整体，特珠对待吧，毕境功能延伸后，可能也要读写model文件。
 * 使用此功能，必须保证前端目录在 public/前端前缀目录/前端文件及目录   例：public/pc/pages/xxx/xxxx.html
 * 前后端分离，不同服务器的，请在同服务器下生成完后调试OK，再分走前端文件去重新部署！
 * @author kaige
 * @update 2022-07-01
 */
class Makecode extends Basic
{
  protected $rootPath;            // 应用根目录
  protected $modulePath;          // 模块目录
  protected $controllerPath;      // 控制器目录
  protected $servicePath;         // 服务层目录
  protected $daoPath;             // 数据层目录
  protected $viewPath;            // 前端目录
  protected $modelPath;           // 数据模型目录
  protected $smPath;              // 应用模块分类目录 如 hrm,sys,pmm

  protected $databaseName;        // 数据库名称
  protected $tableName;           // 数据表名称
  protected $tableComment;        // 数据表备注
  protected $className;           // 类名称 - 去掉表前缀，大驼峰命名
  protected $viewName;            // 视图目录名称 - 去掉前缀,小写，以符号下划线 _ 链接

  protected $author;              // 作者
  protected $ctime;               // 生成时间
  protected $copyright;           // 版权

  protected $isSoftDelete = false;    // 是否有删除标识
  protected $isautotimes = false;     //自动写入时间戳 create_time,update_time
  protected $isczupdateTime = false;  // 是否有 update_time

  protected $tableInfo;


  public function initialize()
  {
    parent::initialize();

    $this->rootPath       = root_path();
    $this->modelPath      = $this->rootPath . '/app/common/model/';
    $this->servicePath    = $this->rootPath . '/app/common/service/';
    $this->daoPath        = $this->rootPath . '/app/common/dao/';
    $this->controllerPath = $this->rootPath . '/app/backend/controller/';
    $this->viewPath       = $this->rootPath . '/public/pc/pages/'; //默认放 pc/ 下

    $this->service = new MakecodeService();
  }

  /**
   * 列表 数据库列表
   */
  public function curd()
  {
    if ($this->request->isGet()) {
      $logdata = $this->service->getListAll();
      $data = Db::query("SHOW TABLE STATUS");
      foreach ($data as $key => $val) {
        if (empty($val['Comment'])) {
          unset($data[$key]);
          continue;
        }
        foreach ($logdata as $k1 => $v1) {
          if ($v1['table_name'] == $val['Name']) {
            $data[$key] = array_merge($data[$key], $logdata[$k1]);
          }
        }
      }
      $data = array_column($data, null);
      return json(array(
        "code" => 0,
        "msg" => "ok",
        "count" => count($data),
        "data" => $data
      ));
    }
    return _jerr(1000);
  }

  /**
   * 列表 数据库列表
   */
  public function index()
  {
    if ($this->request->isGet()) {
      $logdata = $this->service->getListAll();
      $data = Db::query("SHOW TABLE STATUS");
      foreach ($logdata as $key => $val) {
        foreach ($data as $k1 => $v1) {
          if ($val['table_name'] == $v1['Name']) {
            $logdata[$key]['iscz'] = 1;
            $logdata[$key]['comment'] = $v1['Comment'];
          }
        }
      }
      return json(array(
        "code" => 0,
        "msg" => "ok",
        "count" => count($logdata),
        "data" => $logdata
      ));
    }
    return _jerr(1000);
  }

  /**
   * 删除
   */
  public function del()
  {
    if ($this->request->isPost()) {
      $id = input('id', '');
      if ($id === 0) {
        return _jerr(1002);
      }
      return json($this->service->_del($id));
    }
    return _jerr(1000);
  }

  /**
   * 生成器
   */
  public function edit()
  {
    if ($this->request->isPost()) {
      if (config('kaige.open_make_curd') == false) {
        return _jerr('开发者模式方可使用');
      }
      $postdata = $this->request->post();
      url_decode($postdata);
      //表名
      $table_name = $postdata['tableName'];
      $this->tableName = $table_name;

      //作者
      $this->author = $postdata['handle_name'];
      //时间
      $this->ctime = time2str(time(), 2);
      $this->copyright = $postdata['copyright'];

      //检查表名是否合格 sz_xxx_xxx
      $jcn = explode("_", $table_name);
      if (count($jcn) < 3) {
        return _jerr('表名称不符合规则约定1', $postdata);
      }
      if ($jcn[0] . '_' != env('database.prefix')) {
        return _jerr('表名称不符合规则约定2', $postdata);
      }
      //分模块名
      $this->smPath = $jcn[1];

      // $tmpname = str_replace($jcn[0] . '_', '', $table_name);
      // $tmpname = str_replace($jcn[1] . '_', '', $tmpname); // xxxx | xxxx_xxx | xxx_xxxx_xxxx ...

      // 数据库名称
      $this->databaseName = env('database.database');

      $table_info = Db::query("SHOW TABLE STATUS WHERE `Name`='" . $table_name . "'");
      if (!$table_info) {
        return _jerr('发生错误！');
      }
      //数据表备注
      $this->tableComment = $table_info[0]['Comment'];
      if (!$this->tableComment) {
        return _jerr('没有找到对应的数据表，或者表未备注！');
      }

      // 处理类名称、视图名称
      $this->_name($table_name);

      $table_field = Db::query("SELECT COLUMN_NAME,DATA_TYPE,COLUMN_COMMENT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" . $this->tableName . "' AND TABLE_SCHEMA = '" . $this->databaseName . "' ORDER BY ORDINAL_POSITION");
      if (!$table_field) {
        return _jerr('数据库存在异常');
      }
      $this->tableInfo = $table_field;

      $this->modelPath      = $this->modelPath . $this->smPath;
      $this->daoPath        = $this->daoPath . $this->smPath;
      $this->servicePath    = $this->servicePath . $this->smPath;
      $this->controllerPath = $this->controllerPath . $this->smPath;
      $this->viewPath       = $this->viewPath . $this->smPath . '/' . $this->viewName;

      // ------------开始生成--------------
      // 生成数据模型
      $makestep1 = $this->makeModel();
      $makestep2 = $this->makeDao();
      $makestep3 = $this->makeService();
      $makestep4 = $this->makeView();
      $makestep5 = $this->makeController();
      $msg = [
        $makestep1, $makestep2, $makestep3, $makestep4, $makestep5
      ];
      //记录生成
      if ($makestep1['status'] == true) {
        $this->service->add([
          'table_name' => $this->tableName,
          'is_make' => 1,
          'maketype' => 1,
          'author' => $this->author
        ]);
      }
      return _jok('ok', $msg);
    }
    if ($this->request->isGet()) {
      $logdata = $this->service->getListAll();
      if ($logdata) {
        $data = Db::query("SHOW TABLE STATUS");
        foreach ($data as $key => $val) {
          if (empty($val['Comment'])) {
            unset($data[$key]);
            continue;
          }
          foreach ($logdata as $k1 => $v1) {
            if ($v1['table_name'] == $val['Name']) {
              if ($v1['is_make'] == 1) { // 过滤已生成的。
                unset($data[$key]);
              } else {
                if (isset($data[$key])  && isset($logdata[$k1])) {
                  $data[$key] = array_merge($data[$key], $logdata[$k1]);
                }
              }
            }
          }
        }
        $data = array_column($data, null);
        return _jok('ok', [
          'dbTables' => $data,
          'einfo' => (new UserService())->getEmployeeInfo($this->user['id'])
        ]);
      }
      return _jerr('出错了');
    }
  }

  /**
   * 去掉前缀取表名
   */
  protected function _name($name)
  {
    $nameArray = array_map('ucfirst', explode('_', $name));
    unset($nameArray[0]);
    unset($nameArray[1]);
    $this->className = implode($nameArray);
    $this->viewName = strtolower(implode('_', $nameArray));
  }

  /**
   * 生成数据模型
   */
  protected function makeModel()
  {
    //字段内容
    $field_schema = '[' . PHP_EOL;
    foreach ($this->tableInfo as $key => $val) {
      $field_schema .= ' \'' . $val['COLUMN_NAME'] . '\' => \'' . $val['DATA_TYPE'] . '\',' . PHP_EOL;
      if ('delete_time' == $val['COLUMN_NAME']) {
        $this->isSoftDelete = true;
      }
      if ('create_time' == $val['COLUMN_NAME']) {
        $this->isautotimes = true;
      }
      if ('update_time' == $val['COLUMN_NAME']) {
        $this->isczupdateTime = true;
      }
    }
    $field_schema = rtrim($field_schema, "," . PHP_EOL);
    $field_schema .= PHP_EOL . '];' . PHP_EOL;
    // 拼成页面内容
    $template = '<?php

declare(strict_types=1);

namespace app\\common\\model\\' . $this->smPath . ';

use think\Model;
' . ($this->isSoftDelete ? 'use think\model\concern\SoftDelete;' : '') . '
use app\common\traits\ModelTrait;

/**
 * ' . $this->tableComment . ' - 模型
 * @author ' . $this->author . '
 * @update  ' . $this->ctime . '
 * @copyright ' . $this->copyright . '
 */
class ' . ucfirst($this->smPath) . $this->className . ' extends Model
{
  ' . ($this->isSoftDelete ? 'use SoftDelete;
  //软删除时间
  protected $deleteTime = \'delete_time\';' : '') . '
  
  ' . ($this->isautotimes ? '  //开启自动写入时间戳字段
    protected $autoWriteTimestamp = true;
    protected $createTime = \'create_time\';' : '') . '
  ' . ($this->isautotimes ? ($this->isczupdateTime ? 'protected $updateTime = \'update_time\';' : 'protected $updateTime = null;') : '') . '

    // 设置字段信息(避免多一次查询的开销)
    protected $schema = ' . $field_schema . '

    use ModelTrait;
}

';
    //文件路径是否存在，不存在则新建
    $this->isDir($this->modelPath);
    // 文件路径
    $modelfilePath = $this->modelPath . '/' . ucfirst($this->smPath) . $this->className . '.php';

    //文件是否存在，不存在则新建，存在则返回。  注释：is_file该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。
    if (is_file($modelfilePath)) {
      return _err('文件已经存在！已存在的功能不能再生成');
    }

    file_put_contents($modelfilePath, $template);
    return _ok('生成数据模型：' . $modelfilePath . '成功！');
  }

  /**
   * 目录是否存在，不存在则创建
   * @param $dir
   */
  protected function isDir($dir)
  {
    if (!is_dir($dir)) {
      mkdir($dir, 0777, true);
    }
  }

  /**
   * 生成数据访问层
   */
  protected function makeDao()
  {
    $template = '<?php

    declare(strict_types=1);
    
    namespace app\\common\\dao\\' . $this->smPath . ';
    
    use app\\common\\model\\' . $this->smPath . '\\' . ucfirst($this->smPath) . $this->className . ';
    use app\\common\\traits\\DaoTrait;
    
    /**
     * ' . $this->tableComment . ' 数据访问层(Dao)
     * @author ' . $this->author . '
     * @update  ' . $this->ctime . '
     * @copyright ' . $this->copyright . '
     */
    class ' . $this->className . 'Dao
    {
      // 当前操作的模型
      protected $model = null;
    
      // 对应模型的主键，默认为id
      protected $pk = \'id\';
    
      // 对应模型默认查询的字段
      protected $name = \'\';
    
      // 对应模型允许查询的字段(默认全部查询)
      protected $allowField = [\'*\'];
    
      // 允许单字段更新
      protected $uaField = [\'\'];
    
      // 查询结果-要隐藏的字段 默认空数组
      protected $hideField = [];
    
      public function __construct()
      {
        $this->model = new ' . ucfirst($this->smPath) . $this->className . '();
      }
      use DaoTrait;
    }';

    //文件路径是否存在，不存在则新建
    $this->isDir($this->daoPath);
    // 文件路径
    $daofilePath = $this->daoPath . '/' . $this->className . 'Dao.php';

    //文件是否存在，不存在则新建，存在则返回。  注释：is_file该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。
    if (is_file($daofilePath)) {
      return _err('文件已经存在！已存在的功能不能再生成');
    }

    file_put_contents($daofilePath, $template);
    return _ok('生成Dao层：' . $daofilePath . '成功！');
  }

  /**
   * 生成服务业务层
   */
  protected function makeService()
  {
    $template = '<?php

    declare(strict_types=1);
    
    namespace app\\common\\service\\' . $this->smPath . ';
    
    use app\\common\\traits\\ServiceTrait;
    use app\\common\\dao\\' . $this->smPath . '\\' . $this->className . 'Dao;
    
    /**
     * ' . $this->tableComment . ' 服务业务层(Service)
     * @author ' . $this->author . '
     * @update  ' . $this->ctime . '
     * @copyright ' . $this->copyright . '
     */
    class ' . $this->className . 'Service
    {
      protected $dao = null;
    
      public function __construct()
      {
        $this->dao = new ' . $this->className . 'Dao();
      }
      use ServiceTrait;
    
    }
    ';
    //文件路径是否存在，不存在则新建
    $this->isDir($this->servicePath);
    // 文件路径
    $servicefilePath = $this->servicePath . '/' . $this->className . 'Service.php';

    //文件是否存在，不存在则新建，存在则返回。  注释：is_file该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。
    if (is_file($servicefilePath)) {
      return _err('文件已经存在！已存在的功能不能再生成');
    }

    file_put_contents($servicefilePath, $template);
    return _ok('生成Service层：' . $servicefilePath . '成功！');
  }

  /**
   * 生成视图层--前端
   */
  protected function makeView()
  {
    $tableCols = '[[' . PHP_EOL;
    foreach ($this->tableInfo as $key => $val) {
      if ($val['COLUMN_NAME'] != 'delete_time') {
        $tableCols .= '{ field: \'' . $val['COLUMN_NAME'] . '\',title: \'' . $val['COLUMN_COMMENT'] . '\', align: \'center\' },' . PHP_EOL;
      }
    }
    $tableCols .= '{ title: \'操作\', toolbar: \'#dtrbar\', align: \'center\', width: 150, fixed: \'right\' }' . PHP_EOL;
    $tableCols .= ']]';

    $formitemhtml = ''; //表单.. 核心部份
    $fih = 0;
    $formitemhtml .= '<div class="layui-form-item">' . PHP_EOL;
    foreach ($this->tableInfo as $key => $val) {
      if (!in_array($val['COLUMN_NAME'], ['delete_time', 'create_time', 'update_time', 'ctime', 'utime', 'id'])) {
        if ($fih % 2 == 0 && $fih > 0) {
          $formitemhtml .= '</div>' . PHP_EOL . '<div class="layui-form-item">' . PHP_EOL;
        }
        $formitemhtml .= '<label class="layui-form-label">' . $val['COLUMN_COMMENT'] . '</label>' . PHP_EOL;
        $formitemhtml .= '<div class="layui-input-inline">' . PHP_EOL;
        $formitemhtml .= '  <input name="' . $val['COLUMN_NAME'] . '" value="{{ d.' . $val['COLUMN_NAME'] . ' || \'\' }}" class="layui-input" type="text">' . PHP_EOL;
        $formitemhtml .= '</div>' . PHP_EOL;
        $fih++;
      }
    }
    $formitemhtml .= '</div>' . PHP_EOL;

    $template = '<!DOCTYPE html>
    <html>
    
    <head>
      <meta charset="utf-8">
      <meta name="renderer" content="webkit|ie-comp|ie-stand">
      <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
      <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
      <meta http-equiv="Cache-Control" content="no-siteapp" />
      <title>' . $this->tableComment . '</title>
      <script src="../../../static/kaige/js/szload.js"></script>
    </head>
    
    <body>
      <div class="kaige-body kaige-btn-radius">
        <table class="layui-table" id="pageTable" lay-filter="pageTable"></table>
      </div>
    
      <script>
        load_init_js(function () {
          layui.config({
            base: szoapcpath + \'/static/kaige/layuiextend/\'
          }).use([\'kaige\'], function () {
            var element = layui.element,
              layer = layui.layer,
              table = layui.table,
              form = layui.form,
              kaige = layui.kaige,
            $ = layui.$;
            var loadnum = 0;

            kaige.table({
              elem: \'#pageTable\',
              url: "/' . $this->smPath . '.' . $this->viewName . '/index",
              id: \'pageTable\',
              toolbar: \'#dtoolbar\',
              defaultToolbar: [\'filter\'],
              height: \'full-35\',
              cols: ' . $tableCols . ',
              done: function (a, b, c) {
                if (loadnum < 1) {
                  searchInit();
                  loadnum++;
                }
              }
            });

            function searchInit() {
              $("input[name=keywords]").on(\'keyup\', function (e) {
                if (e.keyCode == 13) {
                  $("lay-filter=\'search-btn-submit\'").trigger("click");
                }
              });
    
              form.on(\'submit(search-btn-submit)\', function (data) {
                $where = data.field;
                uwhere = {};
                for (var i in $where) {
                  uwhere[i] = $where[i] ? encodeURIComponent($where[i]) : \'\';
                }
                table.reloadData(\'pageTable\', {
                  height: \'full-35\'
                  , page: {
                    curr: 1
                  }
                  , where: uwhere
                }, true);
                return false;
              });
            }
    
            //列表-监听表头工具栏事件dtoolbar
            table.on(\'toolbar(pageTable)\', function (obj) {
              switch (obj.event) {
                case \'btn-add\':
                  btnadd();
                  break;
              }
            });
    
            //列表-监听单元格行工具事件dtrbar
            table.on(\'tool(pageTable)\', function (obj) {
              var that = this;
              var data = obj.data;
              switch (obj.event) {
                case \'edit\':
                  baredit(data);
                  break;
                case \'del\':
                  bardel(data);
                  break;
              }
            });
    
            //添加
            function btnadd() {
              kaige.form({
                url: "/' . $this->smPath . '.' . $this->viewName . '/add",
                data: {},
                id: \'kaige\',
                area: [\'750px\', \'450px\'],
                title: \'<i class="layui-icon layui-icon-add-circle"></i> 添加 - ' . $this->tableComment . '\',
                tablelayid: \'pageTable\',
                overflow: false,
                encode: 1,
                callbefore: function (layero, index) {
                  
                }
              });
            }
    
            //编辑
            function baredit(d) {
              kaige.form({
                url: "/' . $this->smPath . '.' . $this->viewName . '/edit",
                data: { id: d.id },
                id: \'kaige\',
                area: [\'750px\', \'450px\'],
                title: \'<i class="layui-icon layui-icon-edit"></i> 编辑 - ' . $this->tableComment . '\',
                tablelayid: \'pageTable\',
                overflow: false,
                encode: 1,
                callbefore: function (layero, index) {
                 
                },
              });
            }
    
            //删除
            function bardel(d) {
              layer.confirm(\'您确定删除吗？\', {
                title: \'<i class="layui-icon layui-icon-delete"></i> 删除确认\',
                btn: [\'确认\', \'取消\']
              }, function () {
                $.post("/' . $this->smPath . '.' . $this->viewName . '/del", { id: d.id }, function (res) {
                  res.status && table.reload(\'pageTable\');
                  layer.msg(res.msg);
                });
              });
            };
    
          });
        });
      </script>
      <script type="text/html" id="dtrbar">
        <div class="layui-btn-group">
            <button type="button" class="layui-btn layui-btn-sm layui-bg-blue" lay-event="edit" title="编辑"><i class="layui-icon layui-icon-edit"></i></button>
            <button type="button" class="layui-btn layui-btn-sm layui-bg-red" lay-event="del" title="删除"><i class="layui-icon layui-icon-delete"></i></button>
        </div>
      </script>
      <script type="text/html" id="dtoolbar">
        <div class="layui-form" lay-filter="page_search_box">
          <div class="layui-inline">
            <input class="layui-input tsinput" name="keywords" placeholder="关键字">
            <button class="tsbutton" lay-submit lay-filter="search-btn-submit" title="筛选"><i class="layui-icon layui-icon-search"></i></button>
          </div>
          <div class="layui-btn-group">
            <button type="button" class="layui-btn layui-btn-sm" lay-event="btn-add"><i class="layui-icon layui-icon-add-circle"></i> 添加</button>
          </div>
        </div>
      </script>
      <script type="text/html" id="kaige_form_html">
        <form class="layui-form layui-form-pane kaige-body-form" lay-filter="kaige_form_lay_filter">
          {{# if(d.id){ }}
          <input type="hidden" name="id" value="{{ d.id }}" />
          {{# } }}
          ' . $formitemhtml . '
          <div class="layui-form-item layui-hide">
            <button class="layui-hide" lay-submit="" id="kaige_form_btn_save" lay-filter="kaige_form_btn_save">保存数据</button>
          </div>
        </form>
      </script>
    </body>
    
    </html>';
    //文件路径是否存在，不存在则新建
    $this->isDir($this->viewPath);
    // 文件路径
    $viewfilePath = $this->viewPath . '/index.html'; //默认index.html

    //文件是否存在，不存在则新建，存在则返回。  注释：is_file该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。
    if (is_file($viewfilePath)) {
      return _err('文件已经存在！已存在的功能不能再生成');
    }

    file_put_contents($viewfilePath, $template);
    return _ok('生成前端视图层：' . $viewfilePath . '成功！');
  }

  /**
   * 生成控制器层
   */
  protected function makeController()
  {
    $template = '<?php

    declare(strict_types=1);

    namespace app\\backend\\controller\\' . $this->smPath . ';
    
    use app\\common\\controller\\Basic;
    use app\\common\\service\\' . $this->smPath . '\\' . $this->className . 'Service;
    
    /**
     * ' . $this->tableComment . ' 控制器
     * @author ' . $this->author . '
     * @update  ' . $this->ctime . '
     * @copyright ' . $this->copyright . '
     */
    class ' . $this->className . ' extends Basic
    {
    
      // 初始化
      protected function initialize()
      {
        parent::initialize();
        // 当前控制器服务实例化
        $this->service = new ' . $this->className . 'Service();
      }
    
      /**
       * 列表
       */
      public function index()
      {
        if ($this->request->isGet()) {
          $data = input(\'param.\');
          return json($this->service->_list($data));
        }
        return _jerr(1000);
      }
    
      /**
       * 添加
       */
      public function add()
      {
        if ($this->request->isPost()) {
          $data = input(\'param.\');
          $this->_form_filter($data);
          return json($this->service->saveForm($data));
        }
        if ($this->request->isGet()) {
          $data = [];
          return _jok(\'ok\', $data);
        }
        return _jerr(1000);
      }
    
      /**
       * 编辑
       */
      public function edit()
      {
        $id = input(\'id/d\', 0);
        if ($id === 0) {
          return _jerr(1022);
        }
        if ($this->request->isPost()) {
          $data = input(\'param.\');
          $this->_form_filter($data);
          return json($this->service->saveForm($data));
        }
        if ($this->request->isGet()) {
          $data = $this->service->getInfoById($id);
          return _jok(\'ok\', $data);
        }
        return _jerr(1000);
      }
    
      /**
       * 添加编辑-表单数据处理
       */
      protected function _form_filter(&$data)
      {
        url_decode($data);
      }
    
      /**
       * 删除
       */
      public function del()
      {
        if ($this->request->isPost()) {
          $id = input(\'id\', \'\');
          if ($id === 0) {
            return _jerr(1002);
          }
          return json($this->service->_del($id));
        }
        return _jerr(1000);
      }
    }
    ';
    //文件路径是否存在，不存在则新建
    $this->isDir($this->controllerPath);
    // 文件路径
    $controllerfilePath = $this->controllerPath . '/' . $this->className . '.php';

    //文件是否存在，不存在则新建，存在则返回。  注释：is_file该函数的结果会被缓存。请使用 clearstatcache() 来清除缓存。
    if (is_file($controllerfilePath)) {
      return _err('文件已经存在！已存在的功能不能再生成');
    }

    file_put_contents($controllerfilePath, $template);
    return _ok('生成前端视图层：' . $controllerfilePath . '成功！');
  }
}
